package org.codefilarete.stalactite.sql.statement;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.codefilarete.stalactite.sql.result.ResultSetIterator;
import org.codefilarete.stalactite.sql.statement.binder.ResultSetReader;
import org.codefilarete.tool.collection.Iterables;
import org.codefilarete.tool.function.SerializableThrowingBiFunction;

/**
 * Default implementation of a {@link ResultSet} returned by {@link Statement#getGeneratedKeys()}.
 * It only reads one generated value that must be in the first column of the returned {@link ResultSet}.
 * It assumes that the key is a primitive {@link Long}.
 * As specified by {@link Statement#getGeneratedKeys()}, it expected that a key is returned for each inserted row.
 * 
 * The key is only used to fill the rows returned by {@link #convert(WriteOperation)}, it's not used for ResultSet read.
 * 
 * As databases support differently generated keys feature, you may override some behaviors. For instance if the
 * key is not in the first column or you don't want to read it as a long, you may override {@link #readKey(ResultSet)}.
 * 
 * If only one row is returned even in multi-row statement (Derby behaves like this, see https://issues.apache.org/jira/browse/DERBY-3609),
 * then you may override {@link #convert(WriteOperation)} to simulate multiple rows reading.
 * 
 * @param <I> generated keys type
 * @author Guillaume Mary
 */
public class GeneratedKeysReader<I> {
	
	private final String keyName;
	
	/** Underlying reader of generated value */
	private final ResultSetReader<I> typeReader;
	
	/**
	 * Constructor.
	 * 
	 * @param keyName column name to be read on generated {@link ResultSet}
	 * @param resultSetGetter reader of generated key column
	 */
	public GeneratedKeysReader(String keyName, SerializableThrowingBiFunction<ResultSet, String, I, SQLException> resultSetGetter) {
			this(keyName, ResultSetReader.ofMethodReference(resultSetGetter));
		}
	
	/**
	 * Constructor.
	 * 
	 * @param keyName column name to be read on generated {@link ResultSet}
	 * @param typeReader reader of generated key column
	 */
	public GeneratedKeysReader(String keyName, ResultSetReader<I> typeReader) {
		this.keyName = keyName;
		this.typeReader = typeReader;
	}
	
	public String getKeyName() {
		return keyName;
	}
	
	/**
	 * Method that must be called after insert or update operation to read generated values through {@link PreparedStatement#getGeneratedKeys()}.
	 * Please note that {@link PreparedStatement} should have been created with {@link java.sql.Connection#prepareStatement(String, int)} and
	 * {@link Statement#RETURN_GENERATED_KEYS} argument.
	 * 
	 * 
	 * @param writeOperation any insert operation (can't be a {@link PreparedStatement} because some implementations needs more information (see Derby override)
	 * @return a {@link List} of database-generated keys during operation execution
	 * @throws SQLException in case of reading problem
	 */
	public List<I> convert(WriteOperation writeOperation) throws SQLException {
		try (ResultSet generatedKeys = writeOperation.preparedStatement.getGeneratedKeys()) {
			return convert(generatedKeys);
		}
	}
	
	public List<I> convert(ResultSet generatedKeys) {
		ResultSetIterator<I> iterator = new ResultSetIterator<I>(generatedKeys) {
			@Override
			public I convert(ResultSet rs) throws SQLException {
				return readKey(rs);
			}
		};
		return Iterables.copy(iterator);
	}
	
	protected I readKey(ResultSet rs) throws SQLException {
		return typeReader.get(rs, getKeyName());
	}
}
